// Copyright (c) 2014 Liyong Zou and Int Li
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.



#ifndef FROM_LUA_HPP
#define FROM_LUA_HPP

extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

#include <string>
#include <typeinfo>
#include <memory>
#include <sstream>
#include <iostream>

#ifdef USE_BOOST
#include <boost/shared_ptr.hpp>
using boost::shared_ptr;
#else
using std::shared_ptr;
#endif


namespace luaglue {
template <class T>
class holder
{
public:
    holder()
    {
    }

    holder(const T &v)
        : m_data(new T(v))
    {
    }

    /*
    holder(const holder<T> &other)
        : m_data(new T(*other))
    {
    }
    */

    T* operator->()
    {
        return &*m_data;
    }

    const T* operator->() const
    {
        return &*m_data;
    }

    T& operator*()
    {
        return *m_data;
    }

    const T& operator*() const
    {
        return *m_data;
    }

    operator bool() const
    {
        return (bool)m_data;
    }

    virtual ~holder()
    {
    }

private:
    shared_ptr<T> m_data;
};

/*
template <class T>
class holder<T &>: public holder<T>
{
public:
    holder() {}

    holder(const T &v)
        : holder<T>(v)
    {

    }
};
*/

template <class T>
class holder<const T &>: public holder<T>
{
public:
    holder() 
    {
    }

    holder(const T &v)
        : holder<T>(v)
    {
    }
};

template <>
class holder<const char *>: public holder<std::string>
{
public:
    holder() 
    {
    }

    holder(const char *v)
        : holder<std::string>(v)
    {
    }

    const char *operator*() const
    {
        return (*this)->c_str();
    }
};


template<class T>
class from_lua
{
public:
    inline holder<T> operator()(lua_State *L, int index)
    {
        luaL_newmetatable (L, "import_ref_table"); // push table
        lua_getfield(L, -1, typeid(T).name()); // push light userdata
        holder<T> (*func)(lua_State *, int) = (holder<T> (*)(lua_State *, int))lua_topointer(L, -1);
        lua_pop(L, 2);

        if (func)
        {
            return func(L, index);
        }

        return holder<T>();
    }
};

/*
template<class T>
class from_lua<T&>
{
public:
    inline holder<T &> operator()(lua_State *L, int index)
    {
        holder<T> raw = from_lua<T>()(L, index);
        T t(*raw);
        return raw? holder<T &>(t) : holder<T &>();
    }
};
*/

template<class T>
class from_lua<const T &>
{
public:
    inline holder<const T &> operator()(lua_State *L, int index)
    {
        holder<T> raw = from_lua<T>()(L, index);
        return raw? holder<const T &>(*raw) : holder<const T &>();
    }
};

template<class T>
class from_lua<shared_ptr<T> >
{
public:
    inline holder<shared_ptr<T> > operator()(lua_State *L, int index)
    {
        if (lua_isuserdata(L, index))
        {
            /*
             * 将index转换成为相对于栈底的偏移， 以简化后面的逻辑
             */
            index = index > 0? index : lua_gettop(L) + 1 + index;

            // push userdata's metatable
            if (0 == lua_getmetatable(L, index))
            { // Does not have a metatable
                return holder<shared_ptr<T> >();
            }

            /*
             * 逐级对比metatable， 确认userdata是否是T的子类
             */
            luaL_getmetatable(L, typeid(T).name()); // push SP type metatable
            lua_pushvalue(L, -2); // dup userdata's metatable
            while(1)
            {
                if (lua_compare(L, -1, -2, LUA_OPEQ))
                {
                    lua_pop(L, 2); // 从栈中移除SP（T）metatable， 和对比的metatable
                    break;
                }

                if (0 == lua_getmetatable(L, -1)) // 获取下一级metatable
                {
                    lua_pop(L, 2);
                    return holder<shared_ptr<T> >();
                }

                lua_remove(L, -2); // 从栈中移除比较过的metatable
            }

            /*
             * 现在栈顶压了userdata的metatable
             */
            holder<shared_ptr<T> > val(*static_cast<shared_ptr<T>*>(lua_touserdata(L, index)));
            // push compared metatable
            luaL_getmetatable(L, 
                    (std::string(typeid(shared_ptr<T>).name()) + "_" + typeid(**val).name()).c_str());
            if (lua_compare(L, -1, -2, LUA_OPEQ))
            {
                lua_pop(L, 2);
                return val;
            }
            lua_pop(L, 2);
        }

        return holder<shared_ptr<T> >();
    }
};

template<>
class from_lua<long>
{
public:
    inline holder<long> operator()(lua_State *L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<long>(lua_tonumber(L, index))
                  : holder<long>();
    }
};

template<>
class from_lua<int>
{
public:
    inline holder<int> operator()(lua_State* L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<int>(lua_tonumber(L, index))
                  : holder<int>();
    }
};

/////////////////Int_li////////////////////////////////////
template<>
class from_lua<double>
{
public:
    inline holder<double> operator()(lua_State *L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<double>(lua_tonumber(L, index))
                  : holder<double>();
    }
};

template<>
class from_lua<short>
{
public:
    inline holder<short> operator()(lua_State *L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<short>(lua_tonumber(L, index))
                  : holder<short>();
    }
};

template<>
class from_lua<const char*>
{
public:
    inline holder<const char*> operator()(lua_State *L, int index)
    {
        return (LUA_TSTRING==lua_type(L, index))?
                    holder<const char*>(lua_tostring(L, index))
                  : holder<const char*>();
    }
};

//*******char********
template<>
class from_lua<char>
{
public:
    inline holder<char> operator()(lua_State *L, int index)
    {
        if (LUA_TSTRING == lua_type(L, index))
        {
            const char* str = lua_tostring(L, index);
            char ch = str[0];
            return holder<char>(ch);
        }
        else
        {
            return holder<char>();
        }
    }
};
//******end char*******
template<>
class from_lua<float>
{
public:
    inline holder<float> operator()(lua_State *L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<float>(lua_tonumber(L, index))
                  : holder<float>();
    }
};

template<>
class from_lua<bool>
{
public:
    inline holder<bool> operator()(lua_State *L, int index)
    {
        return lua_isboolean(L, index)?
                    holder<bool>(lua_toboolean(L, index))
                  : holder<bool>();
    }
};

template<>
class from_lua<unsigned>
{
public:
    inline holder<unsigned> operator()(lua_State *L, int index)
    {
        return lua_isnumber(L, index)?
                    holder<unsigned>(lua_tonumber(L, index))
                  : holder<unsigned>();
    }
};


///////*********************************
template<>
class from_lua<std::string>
{
public:
    inline holder<std::string> operator()(lua_State* L, int index)
    {
        const char *buff;
        size_t size;
        if (LUA_TSTRING==lua_type(L, index))
        {
            buff = luaL_tolstring(L, index, &size); // The resulting string is pushed onto the stack and also returned by the function
            lua_pop(L, 1); // pop the resulting string
            return holder<std::string>(std::string(buff, size));
        }
        else
        {
            return holder<std::string>();
        }
    }
};

///////********************************
////////////////end////////////////////////////////

}

#endif // FROM_LUA_HPP

